/*
 * upap.c - User/Password Authentication Protocol.
 *
 * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netif/ppp/ppp_opts.h"
#if PPP_SUPPORT &&                                                             \
    PAP_SUPPORT /* don't build if not configured for use in lwipopts.h */

/*
 * @todo:
 */

#if 0 /* UNUSED */
#include <stdio.h>
#include <string.h>
#endif /* UNUSED */

#include "netif/ppp/ppp_impl.h"

#include "netif/ppp/upap.h"

#if PPP_OPTIONS
/*
 * Command-line options.
 */
static option_t pap_option_list[] = {
    {"hide-password", o_bool, &hide_password, "Don't output passwords to log",
     OPT_PRIO | 1},
    {"show-password", o_bool, &hide_password,
     "Show password string in debug log messages", OPT_PRIOSUB | 0},

    {"pap-restart", o_int, &upap[0].us_timeouttime,
     "Set retransmit timeout for PAP", OPT_PRIO},
    {"pap-max-authreq", o_int, &upap[0].us_maxtransmits,
     "Set max number of transmissions for auth-reqs", OPT_PRIO},
    {"pap-timeout", o_int, &upap[0].us_reqtimeout,
     "Set time limit for peer PAP authentication", OPT_PRIO},

    {NULL}};
#endif /* PPP_OPTIONS */

/*
 * Protocol entry points.
 */
static void upap_init(ppp_pcb *pcb);
static void upap_lowerup(ppp_pcb *pcb);
static void upap_lowerdown(ppp_pcb *pcb);
static void upap_input(ppp_pcb *pcb, u_char *inpacket, int l);
static void upap_protrej(ppp_pcb *pcb);
#if PRINTPKT_SUPPORT
static int upap_printpkt(const u_char *p, int plen,
                         void (*printer)(void *, const char *, ...), void *arg);
#endif /* PRINTPKT_SUPPORT */

const struct protent pap_protent = {PPP_PAP,
                                    upap_init,
                                    upap_input,
                                    upap_protrej,
                                    upap_lowerup,
                                    upap_lowerdown,
                                    NULL,
                                    NULL,
#if PRINTPKT_SUPPORT
                                    upap_printpkt,
#endif /* PRINTPKT_SUPPORT */
#if PPP_DATAINPUT
                                    NULL,
#endif /* PPP_DATAINPUT */
#if PRINTPKT_SUPPORT
                                    "PAP",
                                    NULL,
#endif /* PRINTPKT_SUPPORT */
#if PPP_OPTIONS
                                    pap_option_list,
                                    NULL,
#endif /* PPP_OPTIONS */
#if DEMAND_SUPPORT
                                    NULL,
                                    NULL
#endif /* DEMAND_SUPPORT */
};

static void upap_timeout(void *arg);
#if PPP_SERVER
static void upap_reqtimeout(void *arg);
static void upap_rauthreq(ppp_pcb *pcb, u_char *inp, int id, int len);
#endif /* PPP_SERVER */
static void upap_rauthack(ppp_pcb *pcb, u_char *inp, int id, int len);
static void upap_rauthnak(ppp_pcb *pcb, u_char *inp, int id, int len);
static void upap_sauthreq(ppp_pcb *pcb);
#if PPP_SERVER
static void upap_sresp(ppp_pcb *pcb, u_char code, u_char id, const char *msg,
                       int msglen);
#endif /* PPP_SERVER */

/*
 * upap_init - Initialize a UPAP unit.
 */
static void upap_init(ppp_pcb *pcb) {
  pcb->upap.us_user = NULL;
  pcb->upap.us_userlen = 0;
  pcb->upap.us_passwd = NULL;
  pcb->upap.us_passwdlen = 0;
  pcb->upap.us_clientstate = UPAPCS_INITIAL;
#if PPP_SERVER
  pcb->upap.us_serverstate = UPAPSS_INITIAL;
#endif /* PPP_SERVER */
  pcb->upap.us_id = 0;
}

/*
 * upap_authwithpeer - Authenticate us with our peer (start client).
 *
 * Set new state and send authenticate's.
 */
void upap_authwithpeer(ppp_pcb *pcb, const char *user, const char *password) {

  if (!user || !password)
    return;

  /* Save the username and password we're given */
  pcb->upap.us_user = user;
  pcb->upap.us_userlen = (u8_t)LWIP_MIN(strlen(user), 0xff);
  pcb->upap.us_passwd = password;
  pcb->upap.us_passwdlen = (u8_t)LWIP_MIN(strlen(password), 0xff);
  pcb->upap.us_transmits = 0;

  /* Lower layer up yet? */
  if (pcb->upap.us_clientstate == UPAPCS_INITIAL ||
      pcb->upap.us_clientstate == UPAPCS_PENDING) {
    pcb->upap.us_clientstate = UPAPCS_PENDING;
    return;
  }

  upap_sauthreq(pcb); /* Start protocol */
}

#if PPP_SERVER
/*
 * upap_authpeer - Authenticate our peer (start server).
 *
 * Set new state.
 */
void upap_authpeer(ppp_pcb *pcb) {

  /* Lower layer up yet? */
  if (pcb->upap.us_serverstate == UPAPSS_INITIAL ||
      pcb->upap.us_serverstate == UPAPSS_PENDING) {
    pcb->upap.us_serverstate = UPAPSS_PENDING;
    return;
  }

  pcb->upap.us_serverstate = UPAPSS_LISTEN;
  if (pcb->settings.pap_req_timeout > 0)
    TIMEOUT(upap_reqtimeout, pcb, pcb->settings.pap_req_timeout);
}
#endif /* PPP_SERVER */

/*
 * upap_timeout - Retransmission timer for sending auth-reqs expired.
 */
static void upap_timeout(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (pcb->upap.us_clientstate != UPAPCS_AUTHREQ)
    return;

  if (pcb->upap.us_transmits >= pcb->settings.pap_max_transmits) {
    /* give up in disgust */
    ppp_error(("No response to PAP authenticate-requests"));
    pcb->upap.us_clientstate = UPAPCS_BADAUTH;
    auth_withpeer_fail(pcb, PPP_PAP);
    return;
  }

  upap_sauthreq(pcb); /* Send Authenticate-Request */
}

#if PPP_SERVER
/*
 * upap_reqtimeout - Give up waiting for the peer to send an auth-req.
 */
static void upap_reqtimeout(void *arg) {
  ppp_pcb *pcb = (ppp_pcb *)arg;

  if (pcb->upap.us_serverstate != UPAPSS_LISTEN)
    return; /* huh?? */

  auth_peer_fail(pcb, PPP_PAP);
  pcb->upap.us_serverstate = UPAPSS_BADAUTH;
}
#endif /* PPP_SERVER */

/*
 * upap_lowerup - The lower layer is up.
 *
 * Start authenticating if pending.
 */
static void upap_lowerup(ppp_pcb *pcb) {

  if (pcb->upap.us_clientstate == UPAPCS_INITIAL)
    pcb->upap.us_clientstate = UPAPCS_CLOSED;
  else if (pcb->upap.us_clientstate == UPAPCS_PENDING) {
    upap_sauthreq(pcb); /* send an auth-request */
  }

#if PPP_SERVER
  if (pcb->upap.us_serverstate == UPAPSS_INITIAL)
    pcb->upap.us_serverstate = UPAPSS_CLOSED;
  else if (pcb->upap.us_serverstate == UPAPSS_PENDING) {
    pcb->upap.us_serverstate = UPAPSS_LISTEN;
    if (pcb->settings.pap_req_timeout > 0)
      TIMEOUT(upap_reqtimeout, pcb, pcb->settings.pap_req_timeout);
  }
#endif /* PPP_SERVER */
}

/*
 * upap_lowerdown - The lower layer is down.
 *
 * Cancel all timeouts.
 */
static void upap_lowerdown(ppp_pcb *pcb) {

  if (pcb->upap.us_clientstate == UPAPCS_AUTHREQ) /* Timeout pending? */
    UNTIMEOUT(upap_timeout, pcb);                 /* Cancel timeout */
#if PPP_SERVER
  if (pcb->upap.us_serverstate == UPAPSS_LISTEN &&
      pcb->settings.pap_req_timeout > 0)
    UNTIMEOUT(upap_reqtimeout, pcb);
#endif /* PPP_SERVER */

  pcb->upap.us_clientstate = UPAPCS_INITIAL;
#if PPP_SERVER
  pcb->upap.us_serverstate = UPAPSS_INITIAL;
#endif /* PPP_SERVER */
}

/*
 * upap_protrej - Peer doesn't speak this protocol.
 *
 * This shouldn't happen.  In any case, pretend lower layer went down.
 */
static void upap_protrej(ppp_pcb *pcb) {

  if (pcb->upap.us_clientstate == UPAPCS_AUTHREQ) {
    ppp_error(("PAP authentication failed due to protocol-reject"));
    auth_withpeer_fail(pcb, PPP_PAP);
  }
#if PPP_SERVER
  if (pcb->upap.us_serverstate == UPAPSS_LISTEN) {
    ppp_error(("PAP authentication of peer failed (protocol-reject)"));
    auth_peer_fail(pcb, PPP_PAP);
  }
#endif /* PPP_SERVER */
  upap_lowerdown(pcb);
}

/*
 * upap_input - Input UPAP packet.
 */
static void upap_input(ppp_pcb *pcb, u_char *inpacket, int l) {
  u_char *inp;
  u_char code, id;
  int len;

  /*
   * Parse header (code, id and length).
   * If packet too short, drop it.
   */
  inp = inpacket;
  if (l < UPAP_HEADERLEN) {
    UPAPDEBUG(("pap_input: rcvd short header."));
    return;
  }
  GETCHAR(code, inp);
  GETCHAR(id, inp);
  GETSHORT(len, inp);
  if (len < UPAP_HEADERLEN) {
    UPAPDEBUG(("pap_input: rcvd illegal length."));
    return;
  }
  if (len > l) {
    UPAPDEBUG(("pap_input: rcvd short packet."));
    return;
  }
  len -= UPAP_HEADERLEN;

  /*
   * Action depends on code.
   */
  switch (code) {
  case UPAP_AUTHREQ:
#if PPP_SERVER
    upap_rauthreq(pcb, inp, id, len);
#endif /* PPP_SERVER */
    break;

  case UPAP_AUTHACK:
    upap_rauthack(pcb, inp, id, len);
    break;

  case UPAP_AUTHNAK:
    upap_rauthnak(pcb, inp, id, len);
    break;

  default: /* XXX Need code reject */
    break;
  }
}

#if PPP_SERVER
/*
 * upap_rauth - Receive Authenticate.
 */
static void upap_rauthreq(ppp_pcb *pcb, u_char *inp, int id, int len) {
  u_char ruserlen, rpasswdlen;
  char *ruser;
  char *rpasswd;
  char rhostname[256];
  int retcode;
  const char *msg;
  int msglen;

  if (pcb->upap.us_serverstate < UPAPSS_LISTEN)
    return;

  /*
   * If we receive a duplicate authenticate-request, we are
   * supposed to return the same status as for the first request.
   */
  if (pcb->upap.us_serverstate == UPAPSS_OPEN) {
    upap_sresp(pcb, UPAP_AUTHACK, id, "", 0); /* return auth-ack */
    return;
  }
  if (pcb->upap.us_serverstate == UPAPSS_BADAUTH) {
    upap_sresp(pcb, UPAP_AUTHNAK, id, "", 0); /* return auth-nak */
    return;
  }

  /*
   * Parse user/passwd.
   */
  if (len < 1) {
    UPAPDEBUG(("pap_rauth: rcvd short packet."));
    return;
  }
  GETCHAR(ruserlen, inp);
  len -= sizeof(u_char) + ruserlen + sizeof(u_char);
  if (len < 0) {
    UPAPDEBUG(("pap_rauth: rcvd short packet."));
    return;
  }
  ruser = (char *)inp;
  INCPTR(ruserlen, inp);
  GETCHAR(rpasswdlen, inp);
  if (len < rpasswdlen) {
    UPAPDEBUG(("pap_rauth: rcvd short packet."));
    return;
  }

  rpasswd = (char *)inp;

  /*
   * Check the username and password given.
   */
  retcode = UPAP_AUTHNAK;
  if (auth_check_passwd(pcb, ruser, ruserlen, rpasswd, rpasswdlen, &msg,
                        &msglen)) {
    retcode = UPAP_AUTHACK;
  }
  BZERO(rpasswd, rpasswdlen);

#if 0  /* UNUSED */
    /*
     * Check remote number authorization.  A plugin may have filled in
     * the remote number or added an allowed number, and rather than
     * return an authenticate failure, is leaving it for us to verify.
     */
    if (retcode == UPAP_AUTHACK) {
	if (!auth_number()) {
	    /* We do not want to leak info about the pap result. */
	    retcode = UPAP_AUTHNAK; /* XXX exit value will be "wrong" */
	    warn("calling number %q is not authorized", remote_number);
	}
    }

    msglen = strlen(msg);
    if (msglen > 255)
	msglen = 255;
#endif /* UNUSED */

  upap_sresp(pcb, retcode, id, msg, msglen);

  /* Null terminate and clean remote name. */
  ppp_slprintf(rhostname, sizeof(rhostname), "%.*v", ruserlen, ruser);

  if (retcode == UPAP_AUTHACK) {
    pcb->upap.us_serverstate = UPAPSS_OPEN;
    ppp_notice(("PAP peer authentication succeeded for %q", rhostname));
    auth_peer_success(pcb, PPP_PAP, 0, ruser, ruserlen);
  } else {
    pcb->upap.us_serverstate = UPAPSS_BADAUTH;
    ppp_warn(("PAP peer authentication failed for %q", rhostname));
    auth_peer_fail(pcb, PPP_PAP);
  }

  if (pcb->settings.pap_req_timeout > 0)
    UNTIMEOUT(upap_reqtimeout, pcb);
}
#endif /* PPP_SERVER */

/*
 * upap_rauthack - Receive Authenticate-Ack.
 */
static void upap_rauthack(ppp_pcb *pcb, u_char *inp, int id, int len) {
  u_char msglen;
  char *msg;
  LWIP_UNUSED_ARG(id);

  if (pcb->upap.us_clientstate != UPAPCS_AUTHREQ) /* XXX */
    return;

  /*
   * Parse message.
   */
  if (len < 1) {
    UPAPDEBUG(("pap_rauthack: ignoring missing msg-length."));
  } else {
    GETCHAR(msglen, inp);
    if (msglen > 0) {
      len -= sizeof(u_char);
      if (len < msglen) {
        UPAPDEBUG(("pap_rauthack: rcvd short packet."));
        return;
      }
      msg = (char *)inp;
      PRINTMSG(msg, msglen);
    }
  }

  UNTIMEOUT(upap_timeout, pcb);
  pcb->upap.us_clientstate = UPAPCS_OPEN;

  auth_withpeer_success(pcb, PPP_PAP, 0);
}

/*
 * upap_rauthnak - Receive Authenticate-Nak.
 */
static void upap_rauthnak(ppp_pcb *pcb, u_char *inp, int id, int len) {
  u_char msglen;
  char *msg;
  LWIP_UNUSED_ARG(id);

  if (pcb->upap.us_clientstate != UPAPCS_AUTHREQ) /* XXX */
    return;

  /*
   * Parse message.
   */
  if (len < 1) {
    UPAPDEBUG(("pap_rauthnak: ignoring missing msg-length."));
  } else {
    GETCHAR(msglen, inp);
    if (msglen > 0) {
      len -= sizeof(u_char);
      if (len < msglen) {
        UPAPDEBUG(("pap_rauthnak: rcvd short packet."));
        return;
      }
      msg = (char *)inp;
      PRINTMSG(msg, msglen);
    }
  }

  UNTIMEOUT(upap_timeout, pcb);
  pcb->upap.us_clientstate = UPAPCS_BADAUTH;

  ppp_error(("PAP authentication failed"));
  auth_withpeer_fail(pcb, PPP_PAP);
}

/*
 * upap_sauthreq - Send an Authenticate-Request.
 */
static void upap_sauthreq(ppp_pcb *pcb) {
  struct pbuf *p;
  u_char *outp;
  int outlen;

  outlen = UPAP_HEADERLEN + 2 * sizeof(u_char) + pcb->upap.us_userlen +
           pcb->upap.us_passwdlen;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + outlen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;
  MAKEHEADER(outp, PPP_PAP);

  PUTCHAR(UPAP_AUTHREQ, outp);
  PUTCHAR(++pcb->upap.us_id, outp);
  PUTSHORT(outlen, outp);
  PUTCHAR(pcb->upap.us_userlen, outp);
  MEMCPY(outp, pcb->upap.us_user, pcb->upap.us_userlen);
  INCPTR(pcb->upap.us_userlen, outp);
  PUTCHAR(pcb->upap.us_passwdlen, outp);
  MEMCPY(outp, pcb->upap.us_passwd, pcb->upap.us_passwdlen);

  ppp_write(pcb, p);

  TIMEOUT(upap_timeout, pcb, pcb->settings.pap_timeout_time);
  ++pcb->upap.us_transmits;
  pcb->upap.us_clientstate = UPAPCS_AUTHREQ;
}

#if PPP_SERVER
/*
 * upap_sresp - Send a response (ack or nak).
 */
static void upap_sresp(ppp_pcb *pcb, u_char code, u_char id, const char *msg,
                       int msglen) {
  struct pbuf *p;
  u_char *outp;
  int outlen;

  outlen = UPAP_HEADERLEN + sizeof(u_char) + msglen;
  p = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_HDRLEN + outlen), PBUF_RAM);
  if (NULL == p)
    return;
  if (p->tot_len != p->len) {
    pbuf_free(p);
    return;
  }

  outp = (u_char *)p->payload;
  MAKEHEADER(outp, PPP_PAP);

  PUTCHAR(code, outp);
  PUTCHAR(id, outp);
  PUTSHORT(outlen, outp);
  PUTCHAR(msglen, outp);
  MEMCPY(outp, msg, msglen);

  ppp_write(pcb, p);
}
#endif /* PPP_SERVER */

#if PRINTPKT_SUPPORT
/*
 * upap_printpkt - print the contents of a PAP packet.
 */
static const char *const upap_codenames[] = {"AuthReq", "AuthAck", "AuthNak"};

static int upap_printpkt(const u_char *p, int plen,
                         void (*printer)(void *, const char *, ...),
                         void *arg) {
  int code, id, len;
  int mlen, ulen, wlen;
  const u_char *user, *pwd, *msg;
  const u_char *pstart;

  if (plen < UPAP_HEADERLEN)
    return 0;
  pstart = p;
  GETCHAR(code, p);
  GETCHAR(id, p);
  GETSHORT(len, p);
  if (len < UPAP_HEADERLEN || len > plen)
    return 0;

  if (code >= 1 && code <= (int)LWIP_ARRAYSIZE(upap_codenames))
    printer(arg, " %s", upap_codenames[code - 1]);
  else
    printer(arg, " code=0x%x", code);
  printer(arg, " id=0x%x", id);
  len -= UPAP_HEADERLEN;
  switch (code) {
  case UPAP_AUTHREQ:
    if (len < 1)
      break;
    ulen = p[0];
    if (len < ulen + 2)
      break;
    wlen = p[ulen + 1];
    if (len < ulen + wlen + 2)
      break;
    user = (const u_char *)(p + 1);
    pwd = (const u_char *)(p + ulen + 2);
    p += ulen + wlen + 2;
    len -= ulen + wlen + 2;
    printer(arg, " user=");
    ppp_print_string(user, ulen, printer, arg);
    printer(arg, " password=");
/* FIXME: require ppp_pcb struct as printpkt() argument */
#if 0
	if (!pcb->settings.hide_password)
#endif
    ppp_print_string(pwd, wlen, printer, arg);
#if 0
	else
	    printer(arg, "<hidden>");
#endif
    break;
  case UPAP_AUTHACK:
  case UPAP_AUTHNAK:
    if (len < 1)
      break;
    mlen = p[0];
    if (len < mlen + 1)
      break;
    msg = (const u_char *)(p + 1);
    p += mlen + 1;
    len -= mlen + 1;
    printer(arg, " ");
    ppp_print_string(msg, mlen, printer, arg);
    break;
  default:
    break;
  }

  /* print the rest of the bytes in the packet */
  for (; len > 0; --len) {
    GETCHAR(code, p);
    printer(arg, " %.2x", code);
  }

  return p - pstart;
}
#endif /* PRINTPKT_SUPPORT */

#endif /* PPP_SUPPORT && PAP_SUPPORT */
